package jmetal.problems.adaboost;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Random;

public class Adaboost {
	private int numberOfPositiveImagesTrainning;
	private int numberOfNegativeImagesTrainning;
	private int numberOfPositiveImagesValidation;
	private int numberOfNegativeImagesValidation;
	private int numberOfPositiveImagesTest;
	private int numberOfNegativeImagesTest;
	private int numberOfPositiveImages;
	private int numberOfNegativeImages;
	private Image[] positiveImages;
	private Image[] negativeImages;
	private int[] positiveTestIndex;
	private int[] negativeTestIndex;
	private int[] positiveTrainningIndex;
	private int[] negativeTrainningIndex;
	private int[] positiveValidationIndex;
	private int[] negativeValidationIndex;
	private int numberOfClassifiers;
	private String positiveImagesPath;
	private String negativeImagesPath;
	
	public Adaboost(){
		this.positiveImages = null;
		this.negativeImages = null;
		this.numberOfNegativeImages = 0;
		this.numberOfPositiveImages = 0;
		this.numberOfNegativeImagesTest = 0;
		this.numberOfPositiveImagesTest = 0;
		this.numberOfNegativeImagesTrainning = 0;
		this.numberOfPositiveImagesTrainning = 0;
		this.numberOfPositiveImagesValidation = 0;
		this.numberOfNegativeImagesValidation = 0;
		this.positiveTestIndex = null;
		this.positiveTrainningIndex = null;
		this.negativeTestIndex = null;
		this.negativeTrainningIndex = null;
		this.positiveValidationIndex = null;
		this.negativeValidationIndex = null;
		this.numberOfClassifiers = 0;
		
	}
	
	public void init(int numberOfClassifiers)
	{
		this.numberOfClassifiers =  numberOfClassifiers;
		this.positiveImagesPath = Config.POSITIVE_IMAGES_PATH;
		this.negativeImagesPath = Config.NEGATIVE_IMAGES_PATH;
		this.initImages();
		updateIndex();
	}
	
	public void updateIndex()
	{
		this.updatePositiveIndex();
		this.updateNegativeIndex();
	}
	
	public void updatePositiveIndex()
	{
		
		ArrayList<Integer> positiveSort = new ArrayList<Integer>();
		
		for (int i = 0; i < this.numberOfPositiveImages; i++)
		{
			positiveSort.add(i);	
		}
		
		for (int i = 0; i < this.numberOfPositiveImagesTrainning; i++) {
			int sort = sortNumber(positiveSort.size());
			this.positiveTrainningIndex[i] = positiveSort.get(sort);
			positiveSort.set(sort,positiveSort.get(positiveSort.size()-1));
			positiveSort.remove(positiveSort.size() -1);
		}

		for (int i = 0; i < this.numberOfPositiveImagesValidation; i++) {
			int sort = sortNumber(positiveSort.size());
			this.positiveValidationIndex[i] = positiveSort.get(sort);
			positiveSort.set(sort,positiveSort.get(positiveSort.size()-1));
			positiveSort.remove(positiveSort.size() -1);
		}

		for (int i = 0; i < this.numberOfPositiveImagesTest; i++) {
			int sort = sortNumber(positiveSort.size());
			this.positiveTestIndex[i] = positiveSort.get(sort);
			positiveSort.set(sort,positiveSort.get(positiveSort.size()-1));
			positiveSort.remove(positiveSort.size() -1);
		}
	}
	
	public void updateNegativeIndex()
	{
		ArrayList<Integer> negativeSort = new ArrayList<Integer>();

		for (int i = 0; i < this.numberOfNegativeImages; i++)
			negativeSort.add(i);

		for (int i = 0; i < this.numberOfNegativeImagesTrainning; i++) {
			int sort = sortNumber(negativeSort.size());
			this.negativeTrainningIndex[i] = negativeSort.get(sort);
			negativeSort.set(sort,negativeSort.get(negativeSort.size()-1));
			negativeSort.remove(negativeSort.size() -1);
		}

		for (int i = 0; i < this.numberOfNegativeImagesValidation; i++) {
			int sort = sortNumber(negativeSort.size());
			this.negativeValidationIndex[i] = negativeSort.get(sort);
			negativeSort.set(sort,negativeSort.get(negativeSort.size()-1));
			negativeSort.remove(negativeSort.size() -1);
		}

		for (int i = 0; i < this.numberOfNegativeImagesTest; i++) {
			int sort = sortNumber(negativeSort.size());
			this.negativeTrainningIndex[i] = negativeSort.get(sort);
			negativeSort.set(sort,negativeSort.get(negativeSort.size()-1));
			negativeSort.remove(negativeSort.size() -1);
		}
	}
	
	public void initImages()
	{
		this.initPositiveImages();
		this.initNegativeImages();
		
	}
	
	public void initPositiveImages()
	{
		File file = new File(positiveImagesPath);
		this.numberOfPositiveImages = file.listFiles().length;
		
		//numberOfPositiveImagesTest = (int) (numberOfPositiveImages*Config.TEST_PERCENTAGE);
		this.numberOfPositiveImagesTrainning = (int)(numberOfPositiveImages * Config.TRAINNING_PERCENTAGE);
		this.numberOfPositiveImagesValidation = (int)(numberOfPositiveImages * Config.VALIDATION_PERCENTAGE);
		this.numberOfPositiveImagesTest = this.numberOfPositiveImages - this.numberOfPositiveImagesTrainning 
				                          - this.numberOfPositiveImagesValidation;
		this.positiveTrainningIndex = new int[this.numberOfPositiveImagesTrainning];
		
		this.positiveValidationIndex = new int[this.numberOfPositiveImagesValidation];
		this.positiveTestIndex = new int[this.numberOfPositiveImagesTest];


		this.positiveImages = new Image[this.numberOfPositiveImages];
		
		int i = 0;
		for(File f : file.listFiles())
		{
			positiveImages[i] = new Image();
			positiveImages[i].readImage(f.getPath());
			i++;
		}
		
	}
	
	public void initNegativeImages()
	{
		File file = new File(negativeImagesPath);
		this.numberOfNegativeImages = file.listFiles().length;

		this.numberOfNegativeImagesTrainning = (int)(this.numberOfNegativeImages * Config.TRAINNING_PERCENTAGE);
		this.numberOfNegativeImagesValidation = (int)( this.numberOfNegativeImages * Config.VALIDATION_PERCENTAGE);
		this.numberOfNegativeImagesTest = this.numberOfNegativeImages - this.numberOfNegativeImagesTrainning
										- this.numberOfNegativeImagesValidation;

		this.negativeTrainningIndex = new int[this.numberOfNegativeImagesTrainning];
		this.negativeValidationIndex = new int[this.numberOfNegativeImagesValidation];
		this.negativeTestIndex = new int[this.numberOfNegativeImagesTest];

		this.negativeImages = new Image[this.numberOfNegativeImages];
		int i = 0;
		for(File f : file.listFiles())
		{
			positiveImages[i] = new Image();
			negativeImages[i].readImage(f.getPath());
			i++;
		}
	}
	
	public int sortNumber(int max)
	{
		Random r = new Random();
		return ((int) ((max-1) * ( r.nextDouble())));
	}
	
	
	public void saveClassifier(int run, WeakClassifier[] solution, String algorithmName)
	{
		String fileName = Config.CLASSIFIERS_PATH +" - " +  algorithmName + " - " + numberOfClassifiers + " - " + run + ".txt";
		File file = new File(fileName);
		
		if(file.exists())
		{
			file.delete();
		}
		try {
			file.createNewFile();
			FileOutputStream fos = new FileOutputStream(file);
			ObjectOutputStream oos = new ObjectOutputStream(fos);
			oos.writeObject(solution);
			oos.close();
			fos.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
	public void loadClassifier(int run, WeakClassifier[] solution, String algorithmName)
	{
		String fileName = Config.CLASSIFIERS_PATH +" - " +  algorithmName + " - " + numberOfClassifiers + " - " + run + ".txt";
		File file = new File(fileName);
		
		if(file.exists())
		{
			file.delete();
		}
		try {
			file.createNewFile();
			FileInputStream fis = new FileInputStream(file);
			ObjectInputStream ois = new ObjectInputStream(fis);
			solution = (WeakClassifier [])ois.readObject();
			
			ois.close();
			fis.close();
		} catch (IOException | ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	public double[] trainClassifier(WeakClassifier[] solution)
	{
		double[] result = new double[2];
		int nTruePositive = 0;
		int nTrueNegative = 0;
		int nFalsePositive = 0;
		int nFalseNegative = 0;

		for (int i = 0; i < this.numberOfPositiveImagesTrainning; i++) {
			Boolean positive = true;
			for (int j = 0; j < this.numberOfClassifiers; j++) {
				int value = solution[j].value(this.positiveImages[this.positiveTestIndex[i]]);
				if (value == 0) {
					positive = false;
					break;
				}
			}
			if (positive == true)
				nTruePositive++;
			else
				nFalseNegative++;
		}
		for (int i = 0; i < numberOfNegativeImagesTrainning; i++) {
			Boolean positive = true;
			for (int j = 0; j < this.numberOfClassifiers; j++) {
				int value = solution[j].value(this.negativeImages[this.negativeTestIndex[i]]);
				if (value == 0) {
					positive = false;
					break;
				}
			}
			if (positive == true)
				nFalsePositive++;
			else
				nTrueNegative++;
		}
		result[Config.FALSE_NEGATIVE] = 1.0 - ((double) nTruePositive / (double) numberOfPositiveImagesTest);
		result[Config.FALSE_POSITIVE] = (double) nFalsePositive / (double) this.numberOfNegativeImagesTest;;
		return result;
	}
	
	
	public double[] evaluateClassifier(WeakClassifier[] solution) {
		double[] result = new double[2];
		int nTruePositive = 0;
		int nTrueNegative = 0;
		int nFalsePositive = 0;
		int nFalseNegative = 0;

		for (int i = 0; i < this.numberOfPositiveImagesTest; i++) {
			Boolean positive = true;
			for (int j = 0; j < this.numberOfClassifiers; j++) {
				int value = solution[j].value(this.positiveImages[this.positiveTestIndex[i]]);
				if (value == 0) {
					positive = false;
					break;
				}
			}
			if (positive == true)
				nTruePositive++;
			else
				nFalseNegative++;
		}
		for (int i = 0; i < numberOfNegativeImagesTest; i++) {
			Boolean positive = true;
			for (int j = 0; j < this.numberOfClassifiers; j++) {
				int value = solution[j].value(this.negativeImages[this.negativeTestIndex[i]]);
				if (value == 0) {
					positive = false;
					break;
				}
			}
			if (positive == true)
				nFalsePositive++;
			else
				nTrueNegative++;
		}

//		result[Config.TRUE_POSITIVE] = nTruePositive;
//		result[Config.TRUE_NEGATIVE] = nTrueNegative;
//		result[Config.FALSE_POSITIVE] = nFalsePositive;
//		result[Config.FALSE_NEGATIVE] = nFalseNegative;
//		result[Config.TP_RATE] = (double) nTruePositive / (double) numberOfPositiveImagesTest;
//		result[Config.FP_RATE] = (double) nFalsePositive / (double) this.numberOfNegativeImagesTest;
//		result[BEST_FITNESS] = (nTruePositive + nTrueNegative) - nFalsePositive;
//		result[Config.BEST_FITNESS] = ((double) nTruePositive / this.numberOfPositiveImagesTest)
//							 - ((double) nFalsePositive / this.numberOfNegativeImagesTest);
		result[Config.FALSE_NEGATIVE] = 1.0 - ((double) nTruePositive / (double) numberOfPositiveImagesTest);
		result[Config.FALSE_POSITIVE] = (double) nFalsePositive / (double) this.numberOfNegativeImagesTest;;
		return result;
	}
	
//	public WeakClassifier[] loadClassifier(int run, String algorithmName) {
//		WeakClassifier[] classifier;
//		String file;
//		file = Config.CLASSIFIERS_PATH + algorithmName + this.numberOfClassifiers + "_classifier (" + run + ").txt";
//
//		ArrayList<String> resultantClassifier;
//		String line;
//		
//		InputStream strongClassifier = new FileInputStream(file);
//		
//		
//		if (strongClassifier.is_open()) {
//			while (strongClassifier.good()) {
//				while (getline(strongClassifier, line, ',')){
//					resultantClassifier.add(line);
//				}
//			}
//			strongClassifier.close();
//			classifier = new WeakClassifier[this.numberOfClassifiers];
//			int i;
//			for (i = 0; i < this.numberOfClassifiers; i++) {
//				int fType;
//				stringstream ss(
//						resultantClassifier.at(i + Config.CLASSIFIER_PARAMETER_FEATURE));
//				ss >> fType;
//				double xPos;
//				stringstream ss2(
//						resultantClassifier.at(i + Config.CLASSIFIER_PARAMETER_XPOSITION));
//				ss2 >> xPos;
//				double yPos;
//				stringstream ss3(
//						resultantClassifier.at(i + Config.CLASSIFIER_PARAMETER_YPOSITION));
//				ss3 >> yPos;
//				double width;
//				stringstream ss4(
//						resultantClassifier.at(i + Config.CLASSIFIER_PARAMETER_WIDTH));
//				ss4 >> width;
//				int height;
//				stringstream ss5(
//						resultantClassifier.at(i + Config.CLASSIFIER_PARAMETER_HEIGHT));
//				ss5 >> height;
//				double limiar;
//				stringstream ss6(
//						resultantClassifier.at(i + Config.CLASSIFIER_PARAMETER_LIMIAR));
//				ss6 >> limiar;
//				int polarity;
//				stringstream ss7(
//						resultantClassifier.at(i + Config.CLASSIFIER_PARAMETER_POLARITY));
//				ss7 >> polarity;
//				double weight;
//				stringstream ss8(
//						resultantClassifier.at(i + Config.CLASSIFIER_PARAMETER_WEIGHT));
//				ss8 >> weight;
//				classifier[i].init(fType, xPos, yPos, width, height, limiar,
//						polarity, weight);
//
//			}
//		}
//
//		else
//			cout << "Unable to open file";
//
//		delete file;
//		return classifier;
//
//	}
	
	// GETs and SETs 
	
	public int getNumberOfPositiveImagesTrainning() {
		return numberOfPositiveImagesTrainning;
	}

	public void setNumberOfPositiveImagesTrainning(
			int numberOfPositiveImagesTrainning) {
		this.numberOfPositiveImagesTrainning = numberOfPositiveImagesTrainning;
	}

	public int getNumberOfNegativeImagesTrainning() {
		return numberOfNegativeImagesTrainning;
	}

	public void setNumberOfNegativeImagesTrainning(
			int numberOfNegativeImagesTrainning) {
		this.numberOfNegativeImagesTrainning = numberOfNegativeImagesTrainning;
	}

	public int getNumberOfPositiveImagesValidation() {
		return numberOfPositiveImagesValidation;
	}

	public void setNumberOfPositiveImagesValidation(
			int numberOfPositiveImagesValidation) {
		this.numberOfPositiveImagesValidation = numberOfPositiveImagesValidation;
	}

	public int getNumberOfNegativeImagesValidation() {
		return numberOfNegativeImagesValidation;
	}

	public void setNumberOfNegativeImagesValidation(
			int numberOfNegativeImagesValidation) {
		this.numberOfNegativeImagesValidation = numberOfNegativeImagesValidation;
	}

	public int getNumberOfPositiveImagesTest() {
		return numberOfPositiveImagesTest;
	}

	public void setNumberOfPositiveImagesTest(int numberOfPositiveImagesTest) {
		this.numberOfPositiveImagesTest = numberOfPositiveImagesTest;
	}

	public int getNumberOfNegativeImagesTest() {
		return numberOfNegativeImagesTest;
	}

	public void setNumberOfNegativeImagesTest(int numberOfNegativeImagesTest) {
		this.numberOfNegativeImagesTest = numberOfNegativeImagesTest;
	}

	public int getNumberOfPositiveImages() {
		return numberOfPositiveImages;
	}

	public void setNumberOfPositiveImages(int numberOfPositiveImages) {
		this.numberOfPositiveImages = numberOfPositiveImages;
	}

	public int getNumberOfNegativeImages() {
		return numberOfNegativeImages;
	}

	public void setNumberOfNegativeImages(int numberOfNegativeImages) {
		this.numberOfNegativeImages = numberOfNegativeImages;
	}

	public Image[] getPositiveImages() {
		return positiveImages;
	}

	public void setPositiveImages(Image[] positiveImages) {
		this.positiveImages = positiveImages;
	}

	public Image[] getNegativeImages() {
		return negativeImages;
	}

	public void setNegativeImages(Image[] negativeImages) {
		this.negativeImages = negativeImages;
	}

	public int[] getPositiveTestIndex() {
		return positiveTestIndex;
	}

	public void setPositiveTestIndex(int[] positiveTestIndex) {
		this.positiveTestIndex = positiveTestIndex;
	}

	public int[] getNegativeTestIndex() {
		return negativeTestIndex;
	}

	public void setNegativeTestIndex(int[] negativeTestIndex) {
		this.negativeTestIndex = negativeTestIndex;
	}

	public int[] getPositiveTrainningIndex() {
		return positiveTrainningIndex;
	}

	public void setPositiveTrainningIndex(int[] positiveTrainningIndex) {
		this.positiveTrainningIndex = positiveTrainningIndex;
	}

	public int[] getNegativeTrainningIndex() {
		return negativeTrainningIndex;
	}

	public void setNegativeTrainningIndex(int[] negativeTrainningIndex) {
		this.negativeTrainningIndex = negativeTrainningIndex;
	}

	public int[] getPositiveValidationIndex() {
		return positiveValidationIndex;
	}

	public void setPositiveValidationIndex(int[] positiveValidationIndex) {
		this.positiveValidationIndex = positiveValidationIndex;
	}

	public int[] getNegativeValidationIndex() {
		return negativeValidationIndex;
	}

	public void setNegativeValidationIndex(int[] negativeValidationIndex) {
		this.negativeValidationIndex = negativeValidationIndex;
	}

	public int getNumberOfClassifiers() {
		return numberOfClassifiers;
	}

	public void setNumberOfClassifiers(int numberOfClassifiers) {
		this.numberOfClassifiers = numberOfClassifiers;
	}

	public String getPositiveImagesPath() {
		return positiveImagesPath;
	}

	public void setPositiveImagesPath(String positiveImagesPath) {
		this.positiveImagesPath = positiveImagesPath;
	}
}
